ZENODO

Suplementary Materials

Apendix 1

Stimuli list

Apendix 2

Table 10.

Intensity analysis per speaker

dataWrd %>% 
  filter(substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") %>%
  group_by (speaker, wordType, conditionNorm) %>%
  summarise(n = n(),
            mInt = mean (snd2IntNorm),
            sdInt = sd (snd2IntNorm)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sdInt) %>% 
  pivot_wider(names_from = c(wordType, stats), values_from = "values")

Table 11.

Duration analysis per speaker

dataWrd %>% 
  group_by (speaker, wordType, conditionNorm) %>%
  summarise(n = n(),
            mInt = mean (lDur),
            sdInt = sd (lDur)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sdInt) %>% 
  pivot_wider(names_from = c(wordType, stats), values_from = "values")

Table 12.

A1-H1 intensity difference analysis per speaker

dataWrd %>% 
  filter (
    (substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>%
  group_by (speaker, conditionNorm, wordType) %>%
  summarise (n = n(),
             a1h1m = mean (a1h1Int),
             a1h1sd = sd (a1h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:a1h1sd) %>% 
  pivot_wider(names_from = c(wordType, stats), values_from = "values")

Table 13.

A2-H1 intensity difference analysis per speaker

dataWrd %>% 
  filter (
    (substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>%
  group_by (speaker, conditionNorm, wordType) %>%
  summarise (n = n(),
             a2h1m = mean (a2h1Int),
             a2h1sd = sd (a2h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:a2h1sd) %>% 
  pivot_wider(names_from = c(wordType, stats), values_from = "values")

Table 14.

H2-H1 intensity difference analysis per speaker

dataWrd %>% 
  filter (
    (substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>%
  group_by (speaker, conditionNorm, wordType) %>%
  summarise (n = n(),
             h2h1m = mean (h2h1Int),
             h2h1sd = sd (h2h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:h2h1sd) %>% 
  pivot_wider(names_from = c(wordType, stats), values_from = "values")

Apendix 3

Pulse and intensity profiles

lapply(unique(dataWrd$speaker), function(i){
if (i == "F45") {
  # exclude "j_ilˀi_nno" from F45. Glottal stop
  # exclude "kul_ilo_jd" from F45 - creak on /l/ in the end of a sentence
  tmp <- data %>% filter (speaker == i &
                          word != "j_ilˀ^i_nno" & word != "kul_il^o_jd")
} else{
  tmp <- data %>% filter (speaker == i)
}

# convert intervals between pulses to milliseconds
tmp$plsIntvl <- tmp$plsIntvl * 1000

# plot pulse profiles
tmp %>%
  ggplot (aes (x = pointsNorm, y = plsIntvl)) +
  geom_line (aes(colour = conditionWord)) +
  xlab ("sound timing, % of total, laterals are in [0, 1]") +
  ylab ("pulse interval duration, ms") +
  geom_vline (xintercept = 1, linetype = "dotted", color = "black") +
  geom_vline (xintercept = 0, linetype = "dotted", color = "black") +
  theme (legend.key.width = unit (0.5, "line"),
         legend.position = "none",
         axis.text.x = element_text(size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
  scale_x_continuous(breaks = c(-2, -1, 0, 1, 2, 3), labels = c("-2", "-1", "0", "1", "2", "3")) +
  scale_color_grey(start = 0.8, end = 0.2) +
  annotate ("text", x = -0.7, y = 35, label = "/vowel/", size = 4.5) +
  annotate ("text", x = 0.5, y = 35, label = "/lateral/", size = 4.5) +
  annotate ("text", x = 2, y = 35, label = "/vowel/", size = 4.5) +
  facet_grid (~conditionNorm~wordType, scales = "free")+
  ggtitle(i) ->
  p1

# plot intensity profiles
 tmp %>%
  ggplot (aes (x = pointsNorm, y = intensityOrig)) +
  geom_line (aes(colour = conditionWord)) +
  xlab ("Sound timing, % of total, laterals are in [0, 1]") +
  ylab ("Sound intensity,dB") +
  ylim (40, 85) +
  geom_vline (xintercept = 1, linetype = "dotted", color = "black") +
  geom_vline (xintercept = 0, linetype = "dotted", color = "black") +
  theme (legend.key.width = unit (0.5, "line"),
         #legend.text = element_text(size = rel(0.75)),
         #legend.text = element_blank(),
         #legend.title = element_blank(),
         legend.position = "none",
         axis.text.x = element_text(size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
   scale_x_continuous(breaks = c(-2, -1, 0, 1, 2, 3), labels = c("-2", "-1", "0", "1", "2", "3")) +
  scale_color_grey(start = 0.8, end = 0.2) +
  annotate ("text", x = -1, y = 85, label = "/vowel/", size = 4.5) +
  annotate ("text", x = 0.5, y = 85, label = "/lateral/", size = 4.5) +
  annotate ("text", x = 2, y = 85, label = "/vowel/", size = 4.5) +
  facet_wrap (~conditionNorm~wordType, ncol = 2)  +
  ggtitle(i) ->
   p2
 
 return(list(p1, p2))
})
[[1]]
[[1]][[1]]


[[1]][[2]]



[[2]]
[[2]][[1]]


[[2]][[2]]



[[3]]
[[3]][[1]]


[[3]][[2]]



[[4]]
[[4]][[1]]


[[4]][[2]]



[[5]]
[[5]][[1]]


[[5]][[2]]



[[6]]
[[6]][[1]]


[[6]][[2]]



[[7]]
[[7]][[1]]


[[7]][[2]]



[[8]]
[[8]][[1]]


[[8]][[2]]



[[9]]
[[9]][[1]]


[[9]][[2]]



[[10]]
[[10]][[1]]


[[10]][[2]]

Apendix 4

Model printouts

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"),
          conditionNorm != "Carrier phrase") %>% 
  mutate(cond = conditionNorm,
         speaker = factor(speaker,
                          levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                      "M52", "M60", "M69", "M75")),
         intensity = snd2IntNorm,
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l")) %>% 
  lmer (intensity ~ cond*sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: intensity ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: 855.1

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.4672 -0.4874  0.1502  0.6561  1.9824 

Random effects:
 Groups   Name        Variance Std.Dev.
 speaker  (Intercept)  3.972   1.993   
 Residual             12.930   3.596   
Number of obs: 157, groups:  speaker, 10

Fixed effects:
                            Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)                  68.3717     0.9087  17.8625  75.243  < 2e-16 ***
condIndividual words         -6.8749     0.9861 106.3475  -6.972 2.75e-10 ***
soundl                        1.6607     0.8911 144.6487   1.864   0.0644 .  
condIndividual words:soundl   6.5213     1.1874 144.3826   5.492 1.74e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndInw soundl
cndIndvdlwr -0.576              
soundl      -0.338  0.316       
cndIndwrds:  0.253 -0.500 -0.751
dataWrd %>% 
  mutate(duration = lDur,
         speaker = factor (speaker,
                          levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                      "M52", "M60", "M69", "M75")),
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l"),
         cond = conditionNorm,
         maxPulse = plsMax) %>% 
  lmer (duration ~ cond * sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: duration ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: -1785

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-1.9936 -0.6010 -0.1667  0.5333  5.5146 

Random effects:
 Groups   Name        Variance  Std.Dev.
 speaker  (Intercept) 0.0001662 0.01289 
 Residual             0.0004980 0.02231 
Number of obs: 390, groups:  speaker, 10

Fixed effects:
                              Estimate Std. Error         df t value Pr(>|t|)
(Intercept)                   0.111552   0.006445  40.503004  17.309  < 2e-16
condFree narrative           -0.036647   0.006107 364.719230  -6.001 4.73e-09
condIndividual words          0.025651   0.005623 374.469031   4.562 6.88e-06
soundl                       -0.010490   0.006887 374.469031  -1.523    0.129
condFree narrative:soundl     0.001862   0.007534 374.872343   0.247    0.805
condIndividual words:soundl  -0.002478   0.007952 374.469031  -0.312    0.756
                               
(Intercept)                 ***
condFree narrative          ***
condIndividual words        ***
soundl                         
condFree narrative:soundl      
condIndividual words:soundl    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndFrn cndInw soundl cndFn:
condFrnrrtv -0.682                            
cndIndvdlwr -0.654  0.691                     
soundl      -0.534  0.564  0.612              
cndFrnrrtv:  0.496 -0.638 -0.560 -0.914       
cndIndwrds:  0.463 -0.488 -0.707 -0.866  0.792
dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(cond = conditionNorm,
         speaker = factor (speaker,
                               levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                           "M52", "M60", "M69", "M75")),
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l")) %>% 
  lmer(a1h1Int ~ cond * sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: a1h1Int ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: 822.7

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.4428 -0.6115 -0.0417  0.6604  3.4829 

Random effects:
 Groups   Name        Variance Std.Dev.
 speaker  (Intercept) 13.69    3.700   
 Residual             28.87    5.373   
Number of obs: 132, groups:  speaker, 9

Fixed effects:
                            Estimate Std. Error      df t value Pr(>|t|)    
(Intercept)                    7.812      1.636  12.576   4.777 0.000396 ***
condIndividual words           3.924      1.913  63.086   2.051 0.044385 *  
soundl                        -2.709      1.332 120.473  -2.034 0.044179 *  
condIndividual words:soundl   -3.955      1.933 120.277  -2.046 0.042887 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndInw soundl
cndIndvdlwr -0.530              
soundl      -0.285  0.254       
cndIndwrds:  0.195 -0.442 -0.689
dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(cond = conditionNorm,
         speaker = factor (speaker,
                               levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                           "M52", "M60", "M69", "M75")),
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l")) %>% 
  lmer(a2h1Int ~ cond * sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: a2h1Int ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: 923.6

Scaled residuals: 
     Min       1Q   Median       3Q      Max 
-2.54010 -0.58715  0.08819  0.68865  2.34891 

Random effects:
 Groups   Name        Variance Std.Dev.
 speaker  (Intercept) 35.21    5.934   
 Residual             62.99    7.937   
Number of obs: 132, groups:  speaker, 9

Fixed effects:
                            Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)                 -12.1978     2.5533  13.2051  -4.777 0.000346 ***
condIndividual words          3.0378     2.8837  72.1810   1.053 0.295662    
soundl                       -0.5526     1.9680 120.8816  -0.281 0.779357    
condIndividual words:soundl  -6.4067     2.8549 120.7179  -2.244 0.026646 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndInw soundl
cndIndvdlwr -0.515              
soundl      -0.270  0.250       
cndIndwrds:  0.184 -0.434 -0.689
dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a") &
      is.na (a1h1Int) == FALSE &
      conditionNorm != "Carrier phrase" &
      !(speaker == "M69" & conditionNorm == "Individual words") &
      !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(cond = conditionNorm,
         speaker = factor (speaker,
                               levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                           "M52", "M60", "M69", "M75")),
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l")) %>% 
  lmer(h2h1Int ~ cond * sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: h2h1Int ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: 764.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-2.5323 -0.5911  0.0288  0.5910  3.9947 

Random effects:
 Groups   Name        Variance Std.Dev.
 speaker  (Intercept)  7.859   2.803   
 Residual             18.403   4.290   
Number of obs: 132, groups:  speaker, 9

Fixed effects:
                            Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)                   6.9227     1.2619  12.7354   5.486 0.000113 ***
condIndividual words         -0.7163     1.5065  59.3116  -0.475 0.636202    
soundl                       -1.8395     1.0635 120.5669  -1.730 0.086240 .  
condIndividual words:soundl  -1.0728     1.5429 120.3569  -0.695 0.488192    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndInw soundl
cndIndvdlwr -0.539              
soundl      -0.294  0.256       
cndIndwrds:  0.201 -0.448 -0.689
dataWrd %>% 
  mutate(duration = lDur,
         speaker = factor (speaker,
                          levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                      "M52", "M60", "M69", "M75")),
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l"),
         cond = conditionNorm,
         maxPulse = plsMax) %>% 
  lmer (maxPulse ~ cond * sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: maxPulse ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: -2051.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-1.5578 -0.3844 -0.1034  0.2067  7.7914 

Random effects:
 Groups   Name        Variance  Std.Dev.
 speaker  (Intercept) 8.173e-05 0.00904 
 Residual             2.490e-04 0.01578 
Number of obs: 390, groups:  speaker, 10

Fixed effects:
                              Estimate Std. Error         df t value Pr(>|t|)
(Intercept)                   0.025720   0.004542  36.970265   5.663  1.8e-06
condFree narrative           -0.021775   0.004316 361.634035  -5.045  7.2e-07
condIndividual words          0.009473   0.003976 373.308820   2.383  0.01769
soundl                       -0.015371   0.004869 373.308820  -3.157  0.00172
condFree narrative:soundl     0.012704   0.005327 373.765992   2.385  0.01758
condIndividual words:soundl  -0.008494   0.005623 373.308820  -1.511  0.13173
                               
(Intercept)                 ***
condFree narrative          ***
condIndividual words        *  
soundl                      ** 
condFree narrative:soundl   *  
condIndividual words:soundl    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndFrn cndInw soundl cndFn:
condFrnrrtv -0.684                            
cndIndvdlwr -0.657  0.691                     
soundl      -0.536  0.564  0.612              
cndFrnrrtv:  0.497 -0.638 -0.560 -0.914       
cndIndwrds:  0.464 -0.489 -0.707 -0.866  0.792

Apendix 5

Praat script that extracts:

  • Intensity For each /Ca/ segment containing a target consonant sound (labeled as /C/) we normalized the average intensity of the segment so as the right vowel (/a/) had intensity value in the range [69.5 dB; 70.5 dB]. To do this we used the Scale intensity function in Praat. We then extracted the intensity of the target lateral sound from this segment.
  • Duration As explained above, formant transitions were included into the duration of the lateral; in the absence of transitions, the limits of a lateral were identified by a decrease in intensity.
  • Formant structure For each /Ca/ (target consonant and /a/ vowel) segment where the data on harmonics was available, we extracted the lateral’s H1, H2, A1 and A2 frequencies and intensities. The data was extracted from the central segment of a consonant (the middle of the consonant +/- 10 ms).
  • Pulse profile We annotated glottal pulses for each target sound and plotted them to investigate the timing of the strongest glottal constriction to distinguish between pre- and post-glottlized realizations.
form Data extracton parameters
 # sound, word, production type (token), speaker - interval tiers
 # pulse - point tier
 comment Number of the sound tier
 integer sndTier 1
 comment Number of the word tier
 integer wrdTier 2
 comment Number of the production type tier
 integer tknTier 3
 comment Number of the speaker tier
 integer spkTier 6
 comment Number of the pulse tier
 integer plsTier 7
 comment Path to formant extraction data
 text formantFile _ Formant_Extraction_Parameters.txt
 comment Path to save spectrograms
 text path C:\Users\...
endform

soundInitial = selected ("Sound")
txtGrid = selected ("TextGrid")

Edit

# ---- delete the initial word "Sound " from the file name
select soundInitial
outFileP$ = selected$ ()
len = length: outFileP$
outFileP$ = right$: outFileP$, len - 6

# ---- make output file names
outFileI$ = outFileP$ + "_Intensity.txt" ; intensities between pulses
outFileIN$ = outFileP$ + "_Intensity_Norm.txt" ; normalized intensities between pulses
outFileP$ = outFileP$ + "_Pulses and Formants.txt" ; pulse timings
                           ; formants correspond to target sounds
                           ; f1hz f1db f2hz f2db f3hz f3db
# ---- resample the sound

select soundInitial
Resample... 16000 50
sound = selected("Sound")

# ---- get editor name

select txtGrid
txtGridName$ = selected$ ()
editorName$ = string$(txtGrid)
editorName$ = editorName$ + ". " + txtGridName$

# ---- scaling parameters

pctRightBase = 10 ; base width of a spectrogram in Praat picture
pctRightScale = 0.5 ; base duration of a word for scaling, seconds
pctRight = 0

# ---- variables

nWord = 1 ; word counter
j = 1 ; counter for pulses
fCounter = 0 ; counter for formants
currPulse = 0 ; current pulse time
prevPulse = 0 ; previous pulse time

currWrd$ = "" ; current word which is combined letter by letter as the
#       script runs through the sound layer

currWrdOut$ = "" ; current word for the output file

# ---- get formant extraction parameters

str = Read Strings from raw text file... 'formantFile$'

selectObject (str)
strNum = Get number of strings

fParams$ [1] = "" ; array of formant extraction parameters

if strNum > 1
 for i to strNum
  fParams$ [i] = Get string: i
 endfor
endif

removeObject: str

# ---- create intensity object 

selectObject: sound
intensity = To Intensity... 100 0

# ---- create pitch object

selectObject: sound
pitch = To Pitch... 0.01 75 600

# ---- extract pulses

selectObject: txtGrid
npulses = Get number of points... plsTier

pulses# = zero# (npulses)

for i to npulses
 pulses# [i] = Get time of point: plsTier, i
endfor

nIntervals = Get number of intervals: sndTier ; number of intervals in the sounds tier

# ---- make headers

writeFileLine: outFileP$, "speaker", tab$, "word", tab$, "prodType", tab$, "V1start", tab$, "Cstart", tab$, "V2start", tab$, "V2end", tab$,
                   ..."f0hz", tab$, "f0db", tab$, "h2hz", tab$, "h2db", tab$, "f1hz", tab$, "f1db", tab$,
                   ..."f2hz", tab$, "f2db", tab$, "f3hz", tab$, "f3db", tab$, "snd", "pulseTime", tab$, "snd", tab$, "pulseTime", tab$
writeFileLine: outFileI$, "speaker", tab$, "word", tab$, "prodType", tab$, "V1start", tab$, "Cstart", tab$, "V2start", tab$, "V2end", tab$,
                  ... "V1int", tab$, "Cint", tab$, "V2int", tab$, "CV2int", tab$,
                  ... "CintNorm", tab$, "V2intNorm", tab$, "CV2intNorm", tab$, "snd", tab$, "intBetweenPulses", tab$, "snd", tab$, "intBetweenPulses", tab$
writeFileLine: outFileIN$, "speaker", tab$, "word", tab$, "prodType", tab$, "V1start", tab$, "Cstart", tab$, "V2start", tab$, "V2end", tab$

# ---- the main algorythm

writeInfoLine: ""

for i from 2 to (nIntervals - 1)

 # -- find sounds in the sound tier
 selectObject: txtGrid

 snd1$ = Get label of interval: sndTier, i - 1 ; previous sound
 snd2$ = Get label of interval: sndTier, i
 snd3$ = Get label of interval: sndTier, i + 1 ; next sound

 # limits of the previous sound
 snd1Lft = Get start point: sndTier, i - 1 ; the left point of the interval
 snd1Rght = Get end point: sndTier, i - 1 ; the right point of the interval

 # limits of the current sound
 snd2Lft = Get start point: sndTier, i
 snd2Rght = Get end point: sndTier, i

 # limits of the next sound
 snd3Lft = Get start point: sndTier, i + 1
 snd3Rght = Get end point: sndTier, i + 1
    
 # -- check that the sounds are in the same word
 wLft = Get interval at time: wrdTier, (snd1Lft + snd2Lft) / 2
 wLft$ = Get label of interval: wrdTier, wLft
 wRght = Get interval at time: wrdTier, (snd3Lft + snd3Rght) / 2
 wRght$ = Get label of interval: wrdTier, wRght

 # -- build the word in order to make leftpart_VlateralV_rightpart key  
 if wLft$ <> wRght$
  currWrd$ = ""
 else
   currWrd$ = currWrd$ + snd1$
 endif

 if snd2$ = "l'"
  snd2$ = "lˀ"
 endif
  if snd2$ = "ll'"
   snd2$ = "llˀ"
  endif

 # -- check whether the lateral is between 2 sounds and that they all are within one word
 # -- then extract all the necessary data

  # this is because we need to have one vowel at each side of the lateral
  if (snd2$ = "l" or snd2$ = "lˀ") and snd1$ <> "" and snd3$ <> "" and wLft$ = wRght$ and
  ...((length (snd1$) = 1 and length (snd3$) = 1) or 
  ...(left$(snd3$,1) = "^" and length (snd1$) = 1) or
  ...(left$(snd1$,1) = "^" and length (snd3$) = 1))

  # make output file name
  currWrdOut$ = left$ (currWrd$, length (currWrd$) - length(snd1$)) ; length is reduced, because there is already the left vowel in currWrd$
  currWrdOut$ = currWrdOut$ + "_" + snd1$ + snd2$ + snd3$ + "_"
  z = i + 2
  tmpsnd$ = Get label of interval: sndTier, z
  tmpsndLft = Get start point: sndTier, z ; the left point of the interval
  tmpsndRght = Get end point: sndTier, z ; the right point of the interval
  tmpwrd = Get interval at time: wrdTier, (tmpsndLft + tmpsndRght) / 2
  tmpwrd$ = Get label of interval: wrdTier, tmpwrd
  #appendInfoLine: "i = ", fixed$(i, 0), " z = ", fixed$(z, 0), " tmpsnd ", tmpsnd$, " tmpwrd ", tmpwrd$
  while tmpwrd$ = wLft$ and z <= nIntervals
   currWrdOut$ = currWrdOut$ + tmpsnd$
   z = z + 1
   tmpsnd$ = Get label of interval: sndTier, z
   tmpsndLft = Get start point: sndTier, z ; the left point of the interval
   tmpsndRght = Get end point: sndTier, z ; the right point of the interval
   tmpwrd = Get interval at time: wrdTier, (tmpsndLft + tmpsndRght) / 2
   tmpwrd$ = Get label of interval: wrdTier, tmpwrd
   #appendInfoLine: "i = ", tab$, fixed$ (i, 0), "z = ", tab$, fixed$(z, 0), tab$, "tmpsnd", tab$, tmpsnd$, tab$,
               ... "tmpwrd", tab$, tmpwrd$, tab$, "currWrdOut", tab$, currWrdOut$, tab$, "currWrd", tab$, currWrd$
  endwhile

  # borders of the target word
  wLftBorder = Get start point: wrdTier, wLft
  wRghtBorder = Get end point: wrdTier, wLft

  currPulse = pulses# [1]

  # skip all the pulses outside the desired sound
  j = 1     
  while j <= npulses - 1 and currPulse < snd1Lft
   j = j + 1
   currPulse = pulses# [j]
  endwhile

  # we are in the first pulse of a desired VCV segment      
  prevPulse = currPulse
  j = j + 1
  currPulse = pulses# [j]

  # file structure: word, token, start of sound 1, start of sound 2,
  # start of sound 3, end of sound 3, sound, pulse, sound, pulse...
  # get the production type (token) data

  tkn = Get interval at time: tknTier, prevPulse
  tkn$ = Get label of interval: tknTier, tkn
  spk = Get interval at time: spkTier, prevPulse
  spk$ = Get label of interval: spkTier, spk

  # write general data
  appendFile: outFileP$, spk$, tab$, currWrdOut$, tab$, tkn$, tab$, fixed$ (snd1Lft, 4), tab$, fixed$ (snd2Lft, 4), tab$, fixed$ (snd3Lft, 4), tab$, fixed$ (snd3Rght, 4), tab$
  appendFile: outFileI$, spk$, tab$, currWrdOut$, tab$, tkn$, tab$, fixed$ (snd1Lft, 4), tab$, fixed$ (snd2Lft, 4), tab$, fixed$ (snd3Lft, 4), tab$, fixed$ (snd3Rght, 4), tab$
  appendFile: outFileIN$, spk$, tab$, currWrdOut$, tab$, tkn$, tab$, fixed$ (snd1Lft, 4), tab$, fixed$ (snd2Lft, 4), tab$, fixed$ (snd3Lft, 4), tab$, fixed$ (snd3Rght, 4), tab$

  # extract and write mean intensities of each sound
  selectObject: intensity
  snd1Int = Get mean... snd1Lft snd2Lft dB
  appendFile: outFileI$, fixed$ (snd1Int, 4), tab$
  snd2Int = Get mean... snd2Lft snd3Lft dB
  appendFile: outFileI$, fixed$ (snd2Int, 4), tab$
  snd3Int = Get mean... snd3Lft snd3Rght dB
  appendFile: outFileI$, fixed$ (snd3Int, 4), tab$
  snd2snd3Int = Get mean... snd2Lft snd3Rght dB
  appendFile: outFileI$, fixed$ (snd2snd3Int, 4), tab$
  
  # extract and write normalized intensities
  editor: editorName$
   Select: snd2Lft - 0.02, snd3Rght + 0.02
   Zoom to selection
   Extract selected sound (preserve times)
  endeditor

  tmpSound = selected ("Sound")

  appendInfoLine: "snd2Int = ",fixed$(snd2Int, 2), " snd3Int = ", fixed$(snd3Int, 2), " snd2snd3Int = ", fixed$(snd2snd3Int, 2)

  snd3IntNorm = 70
  snd2IntNorm = snd2Int + (snd3IntNorm - snd3Int)
  snd2snd3IntNorm = snd2snd3Int + (snd3IntNorm - snd3Int)


  loopFlag = 1

  while loopFlag = 1
   appendInfoLine: "snd2IntTarget = ",fixed$(snd2IntNorm, 2), " snd3IntTarget = ", fixed$(snd3IntNorm, 2), " snd2snd3IntTarget = ", fixed$(snd2snd3IntNorm, 2)
   selectObject: tmpSound
   Scale intensity... snd2snd3IntNorm
   tmpIntensity = To Intensity... 100 0

   snd2IntNorm = Get mean... snd2Lft snd3Lft dB
   snd3IntNorm = Get mean... snd3Lft snd3Rght dB
   if snd3IntNorm < 69.5
    snd2snd3IntNorm = snd2snd3IntNorm + 1
   endif
   if snd3IntNorm > 70.5
    snd2snd3IntNorm = snd2snd3IntNorm - 1
   endif
   if snd3IntNorm >= 69.5 and snd3IntNorm <= 70.5
    loopFlag = 0
   endif
   appendInfoLine: "snd2IntResult = ",fixed$(snd2IntNorm, 2), " snd3IntResult = ", fixed$(snd3IntNorm, 2), " snd2snd3IntResult = ", fixed$(snd2snd3IntNorm, 2)
   removeObject: tmpIntensity
  endwhile

  appendFile: outFileI$, fixed$ (snd2IntNorm, 4), tab$
  appendFile: outFileI$, fixed$ (snd3IntNorm, 4), tab$
  appendFile: outFileI$, fixed$ (snd2snd3IntNorm, 4), tab$


  removeObject: tmpSound
  
  # extract formant data

  if fParams$ [nWord] <> "-"

   # structure of parameters: place within the l/l' sound (0.5 = middle), f1 band leftmost point,
   # f1 band rightmost point, f2 left, f2 right, f3 left, f3 right

   @split (" ", fParams$ [nWord])

   sndPlace = number (split.array$[1])
   f1Lfthz = number (split.array$[2])
   f1Rghthz = number (split.array$[3])
   f2Lfthz = number (split.array$[4])
   f2Rghthz = number (split.array$[5])
   f3Lfthz = number (split.array$[6])
   f3Rghthz = number (split.array$[7])
  else
   sndPlace = 0.5
  endif

  # estimate f0 frequency. automatic estimation by Praat doesn't always work on creaky sounds
  tLeft = snd2Lft + (snd2Rght - snd2Lft) * sndPlace - 0.01
  tRight = snd2Lft + (snd2Rght - snd2Lft) * sndPlace + 0.01
  z = j ; j is the first pulse within the lateral
  pulseSum = 0
  pulseNum = 0
  #appendInfoLine: currWrdOut$, " j = ", fixed$(j, 0), " tLeft ", fixed$(tLeft, 3), " tRight ", fixed$ (tRight, 3), " pulse[j] = ", fixed$(pulses#[j], 3)
  while pulses#[z] < tRight
   #appendInfoLine: "z = ", fixed$(z, 0), " pulse[z] = ", fixed$(pulses#[z], 3)
   if pulses#[z] > tLeft
    pulseSum = pulseSum + pulses#[z] - pulses#[z - 1]
    pulseNum = pulseNum + 1
   endif
   z = z + 1
  endwhile
  if pulseSum = 0
   f0hz = undefined
  else
   pulseSum = pulseSum + pulses#[z] - pulses#[z - 1]
   pulseNum = pulseNum + 1
   f0hz = 1 / (pulseSum / pulseNum)
  endif

  editor: editorName$
   Select: snd2Lft + (snd2Rght - snd2Lft) * sndPlace - 0.01, snd2Lft + (snd2Rght - snd2Lft) * sndPlace + 0.01 ; extract several pulses
   View spectral slice
  endeditor             

  slice = selected ("Spectrum")
  To Ltas (1-to-1)
  ltas = selected("Ltas")
   
  if fParams$ [nWord] <> "-"

   if f0hz <> undefined
    f0Lfthz = f0hz * 0.9
    f0Rghthz = f0hz * 1.1
    f0db = Get maximum... f0Lfthz f0Rghthz None
    f0hz = Get frequency of maximum... f0Lfthz f0Rghthz None

    h2hz = f0hz * 2
    h2Lefthz = h2hz * 0.9
    h2Rghthz = h2hz * 1.1
    h2db = Get maximum... h2Lefthz h2Rghthz None
   else
    f0db = undefined
    f0hz = undefined
    h2hz = undefined
    h2db = undefined
   endif
   f1db = Get maximum... f1Lfthz f1Rghthz None
   f1hz = Get frequency of maximum... f1Lfthz f1Rghthz None

   f2db = Get maximum... f2Lfthz f2Rghthz None
   f2hz = Get frequency of maximum... f2Lfthz f2Rghthz None

   f3db = Get maximum... f3Lfthz f3Rghthz None
   f3hz = Get frequency of maximum... f3Lfthz f3Rghthz None

   # write formant data
   appendFile: outFileP$, fixed$ (f0hz,0), tab$, fixed$ (f0db,0), tab$,
    ...fixed$ (h2hz,0), tab$, fixed$ (h2db,0), tab$,
    ...fixed$ (f1hz, 0), tab$, fixed$ (f1db, 0), tab$,
    ...fixed$ (f2hz, 0), tab$, fixed$ (f2db, 0), tab$,
    ...fixed$ (f3hz, 0), tab$, fixed$ (f3db, 0), tab$
  else
   appendFile: outFileP$, "-", tab$, "-", tab$,
    ..."-", tab$, "-", tab$,
    ..."-", tab$, "-", tab$,
    ..."-", tab$, "-", tab$,
    ..."-", tab$, "-", tab$
  endif

  # draw the spectral slice:
  selectObject: slice
  Select outer viewport: 0, 4, 0, 3
  Erase all
  Draw: 0, 5000, 0, 0, "yes"
  # add formants

  if fParams$ [nWord] <> "-"
   One mark bottom: f1hz, "no", "yes", "yes", "F1 " + fixed$ (f1hz, 0)
   One mark bottom: f2hz, "no", "yes", "yes", "F2 " + fixed$ (f2hz, 0)
   One mark bottom: f3hz, "no", "yes", "yes", "F3 " + fixed$ (f3hz, 0)
  endif

  # save the slice
  selectObject: txtGrid
  txtGridName$ = selected$ ()
  txtGridName$ = right$ (txtGridName$, length (txtGridName$) - 9)

  x = (snd2Lft + snd3Lft) / 2

  fname$ = path$ + txtGridName$ + ", " + currWrdOut$ + ", " + tkn$ + "_slice" + ".png"
  if fileReadable (fname$)
   z = 2
   fname$ = path$ + txtGridName$ + ", " + currWrdOut$ + "_" + string$(z) + ", " + tkn$ + "_slice" + ".png"
   while fileReadable (fname$)
    z = z + 1
    fname$ = path$ + txtGridName$ + ", " + currWrdOut$ + "_" + string$(z) + ", " + tkn$ + "_slice" + ".png"
   endwhile
   Save as 300-dpi PNG file: fname$
  else
   Save as 300-dpi PNG file: fname$
  endif

  # clear the drawing pane
  Erase all

  # ---- extract the sound and textgrid

  # extract the sound and textgrid

  editor: editorName$
   Select: wLftBorder, wRghtBorder
   Zoom to selection
   Extract selected sound (time from 0)
  endeditor
  
  tmpSound = selected ("Sound")

  editor: editorName$
   Extract selected TextGrid (time from 0)
  endeditor

  tmpTxtGrid = selected ("TextGrid")
  
  select tmpTxtGrid
  tmpTxtGridName$ = selected$ ()
  tmpEditorName$ = string$(tmpTxtGrid)
  tmpEditorName$ = tmpEditorName$ + ". " + tmpTxtGridName$

  # edit the textgrid. delete extra pulses

  nPulses = Get number of points: plsTier

  z = 1
  while z <= nPulses
   t = Get time of point: plsTier, z
   if (t < snd1Lft - wLftBorder) or (t > snd3Rght - wLftBorder)
    Remove point: plsTier, z
    nPulses = Get number of points: plsTier
    else
    z = z + 1
   endif
  endwhile

  # remove the data that is not required
  numberOfTiers = Get number of tiers
  if numberOfTiers = 8
    Remove tier... 8
  endif
  Remove tier... 5
  Remove tier... 4

  # ---- draw the spectrogram ----

  # scaling
  pctRight = pctRightBase * (wRghtBorder - wLftBorder) / pctRightScale
  Select outer viewport: 0, pctRight, 0, 4

  # draw the spectrogram
  select tmpSound
  plus tmpTxtGrid

  Edit

  x = snd2Lft + (snd2Rght - snd2Lft) * sndPlace - wLftBorder

  editor: tmpEditorName$
   Select: 0, wRghtBorder - wLftBorder
   # parameters: erase first, write name, draw selection times
   # draw selection hairs, garnish
   Paint visible spectrogram: "yes", "no", "yes", "yes", "yes"
  endeditor

  if fParams$ [nWord] <> "-"
   if f0hz <> undefined   
    One mark left: f0hz, "yes", "yes", "yes", ""
   endif
   One mark left: f1hz, "yes", "yes", "yes", ""
   One mark left: f2hz, "yes", "yes", "yes", ""
   One mark left: f3hz, "yes", "yes", "yes", ""
   Paint circle: "red", x, f1hz, 0.002
   Paint circle: "red", x, f2hz, 0.002
   Paint circle: "red", x, f3hz, 0.002
  endif

  Select outer viewport: 0, pctRight, 4, 7
  
  editor: tmpEditorName$
   Draw visible sound and TextGrid: "no", "no", "yes", "yes", "yes"
  endeditor

  # -- save everything

  Select outer viewport: 0, pctRight, 0, 7

  # the spectrogram
  fname$ = left$ (fname$, length(fname$) - 10) + ".png"
  Save as 300-dpi PNG file: fname$

  # the sound
  fname$ = left$ (fname$, length (fname$) - 4) + ".wav"
  selectObject: tmpSound
  Save as WAV file: fname$

  # the textGrid
  fname$ = left$ (fname$, length (fname$) - 4) + ".TextGrid"
  selectObject: tmpTxtGrid
  Save as short text file: fname$

  # ---- extract data for intensity analysis
  select sound
  plus txtGrid


  editor: editorName$
   Select: snd1Lft, snd3Rght ; in order not to distort intensity
   Zoom to selection
   Extract selected sound (preserve times)
  endeditor
        
  tmpSndI = selected ("Sound")

  select tmpSndI
  Scale intensity... 70
  tmpInt = To Intensity... 100 0

  # ---- add normalized intensities to the output file
  selectObject: tmpInt
  # add mean intensities of each sound
  mean_int = Get mean... snd1Lft snd2Lft dB
  appendFile: outFileIN$, fixed$ (mean_int, 4), tab$
  mean_int = Get mean... snd2Lft snd3Lft dB
  appendFile: outFileIN$, fixed$ (mean_int, 4), tab$
  mean_int = Get mean... snd3Lft snd3Rght dB
  appendFile: outFileIN$, fixed$ (mean_int, 4), tab$

  # ---- add data corresponding to pulses

  # write pulses into the file
  if j <= npulses - 1
   while j <= npulses - 1 and currPulse < snd3Rght

    # first, write the sound
    if prevPulse < snd2Lft
     appendFile: outFileP$, snd1$, tab$
     appendFile: outFileI$, snd1$, tab$
     appendFile: outFileIN$, snd1$, tab$
    else
     if prevPulse < snd3Lft
      appendFile: outFileP$, snd2$, tab$
      appendFile: outFileI$, snd2$, tab$
      appendFile: outFileIN$, snd2$, tab$
     else
      appendFile: outFileP$, snd3$, tab$
      appendFile: outFileI$, snd3$, tab$
      appendFile: outFileIN$, snd3$, tab$
     endif
    endif

    # write intensity
    selectObject: intensity
    mean_int = Get mean... prevPulse currPulse dB
    appendFile: outFileI$, fixed$ (mean_int, 4), tab$

    # write normalized intensity
    selectObject: tmpInt
    mean_int = Get mean... prevPulse currPulse dB
    appendFile: outFileIN$, fixed$ (mean_int, 4), tab$

    # write the last pulse into the output
    appendFile: outFileP$, fixed$ (prevPulse, 4), tab$
    j = j + 1
    prevPulse = currPulse
    currPulse = pulses# [j]
   endwhile
   appendFileLine: outFileI$
   appendFileLine: outFileIN$
            
   # add timing for the last pulse
   appendFileLine: outFileP$, snd3$, tab$, fixed$ (prevPulse, 4)

  endif

  nWord = nWord + 1

  # clear the trash
  removeObject: slice
  removeObject: ltas
  removeObject: tmpSndI
  removeObject: tmpInt
  removeObject: tmpTxtGrid
  removeObject: tmpSound
 endif
endfor

removeObject: intensity
removeObject: pitch
removeObject: sound

selectObject: soundInitial, txtGrid

appendInfoLine: "Done!"

# .sep$ - separator, .str$ the string to be separated
procedure split (.sep$, .str$)
    .strlen = length (.str$)
    .sep = index (.str$, .sep$)
    .seplen = length (.sep$)
    .length = 1
    while .sep > 0
        .part$ = left$ (.str$, .sep - 1)
        .str$ = right$ (.str$, length (.str$) - .sep - .seplen + 1)
        .sep = index (.str$, .sep$)
        .array$ [.length] = .part$
        .length = .length + 1
    endwhile
    .array$ [.length] = .str$
endproc

Code for graphs and statistical modeling

Figure 1

A spectrogram of the /olˀa/ segment in /rolˀal/ (“clothes”) produced by the speaker F 45 in the first individual utterance of the word. Vertical lines on the waveform correspond to glottal pulses.

textgrid_to_df("data/F45_clothes_1_utterance.TextGrid") %>% 
  filter(tier %in% c(1, 5)) %>% 
  mutate(content = str_replace(content, "\\^a", "a")) ->
  tg
draw_sound("data/F45_clothes_1_utterance.wav",
           annotation = tg, 
           from = 0.10304, 
           to = 0.415985, 
           spectrum_info = FALSE)

Figure 2

Pulse profile corresponding to the spectrogram in Fig. 1. Black dots show the timing of each glottal pulse. Red dots always project into the middle of the interval between the two black dots. The red line shows the lengthening of the pulses in response to glottalization. The lower horizontal axis shows original pulse timing within the sound, the upper horizontal axis shows normalized timing, where the lateral is in the interval [0, 1]. Vertical dashed lines delimit sounds of the segment.

data %>% 
  mutate(conditionNorm  = factor(conditionNorm, levels = c("Individual words", "Carrier phrase", "Free narrative"))) ->
  data

tmp <- subset (data, speaker == "F45" & condition == "1" & word == "r_olˀ^a_l")

# points is a variable containing middles of intervals between pulses
# the lateral begins in point 0
# pointsOrig - middles of intervals with the beginning of the first vowel in point 0.
# pointsNorm is a variable with normalized middles of intervals between pulses,
# where the lateral is in [0, 1]
 
# move middles of intervals so that the left vowel begins at point 0
tmp$points <- tmp$points - dataWrd$snd1Bgn[dataWrd$speaker == "F45" & dataWrd$condition == 1 & dataWrd$word == "r_olˀ^a_l"]

# convert to milliseconds
tmp$points <- tmp$points * 1000
tmp$plsIntvl <- tmp$plsIntvl * 1000
tmp$pointsOrig <- tmp$pointsOrig * 1000

# line coordinates
ln1 <- data.frame (xLine1 = c (tmp$pointsOrig [26], tmp$pointsOrig [27]),
                   yLine1 <- c (0, 0))

ln2 <- data.frame (xLine2 = rep (mean (ln1$xLine1), 2),
                   yLine2 = c (0, ln1$xLine1[2] - ln1$xLine1[1]))

ln3 <- data.frame (xLine3 = c((ln1$xLine1[2] + ln1$xLine1[1]) / 2, 0.19 * 1000),
                   yLine3 = c(ln2$yLine2[2] / 2, 0.014 * 1000))

tmp2 <- subset (dataWrd, speaker == "F45" & condition == "1" & word =="r_olˀ^a_l")
tmp2$snd1Bgn <- tmp2$snd1Bgn * 1000
tmp2$snd2Bgn <- tmp2$snd2Bgn * 1000
tmp2$snd3Bgn <- tmp2$snd3Bgn * 1000

# black dots (pulse timing)
tmp3 <- data.frame (pointsOrig = c (tmp$pointsOrig, tmp$pointsOrig[nrow(tmp)] + tmp$plsIntvl[nrow(tmp)]))
tmp3 <-
  tmp3 %>% filter (tmp3$pointsOrig > 50 & tmp3$pointsOrig < 251)
tmp3$pointsOrig[nrow(tmp3)] = 249.9
tmp3$label <- ""
tmp3$label[17] <- "t[i]"
tmp3$label[18] <- "t[i+1]"

tmp %>% 
  ggplot(aes (x = points, y = plsIntvl)) +
  geom_line (aes(colour = condition)) +
  geom_point(aes(colour = condition)) +
  xlab ("VCV segment timing, ms") +
  ylab ("interval between pulses, ms") +
  ylim (0, 50) +
  geom_vline(xintercept = -tmp2$snd1Bgn, linetype = "dashed",
             size = 0.5) +
  annotate ("text", x = 70, y = 30, label = "/o/", size = 6) +
  annotate ("text", x = 150, y = 30, label = "/lˀ/", size = 6) +
  annotate ("text", x = 235, y = 30, label = "/a/", size = 6) +
  geom_vline(xintercept = -tmp2$snd1Bgn + tmp2$snd3Bgn, linetype = "dashed",
             size = 0.5) +
  theme(legend.title=element_blank(), legend.position = "bottom",
        axis.text.x = element_text(size = 18),
        axis.text.y = element_text(size = 18),
        axis.title.x = element_text(size = 18),
        axis.title.y = element_text(size = 18),
        legend.text = element_text(size = 18)) +
  geom_point (data = tmp3, aes (x = pointsOrig,
                               fill = "timing of glottal pulses, ms"), y = 0) +
  geom_text(data = tmp3, aes (x = pointsOrig-1, label = label), parse = TRUE, size = 6, y = 3)+
  geom_line (data = ln1, aes(x = xLine1, y = yLine1), size = 0.2) +
  geom_line (data = ln2, aes(x = xLine2, y = yLine2), arrow = arrow(length=unit(0.2,"cm"), ends="both", type = "closed"), size = 0.2) +
  geom_line (data = ln3, aes(x = xLine3, y = yLine3), arrow = arrow(length=unit(0.2,"cm"), ends="first", type = "closed"), size = 0.2) +
  scale_color_hue(labels = c("duration of intervals between pulses, ms")) +
  scale_fill_discrete(labels = c("glottal pulse timing, ms")) +
  annotate ("label", x = ln3$xLine3[2] + 2,
            y = ln3$yLine3[2] + 2,
            label = "t[i+1] - t[i]", size = 6, parse = TRUE)+
  scale_x_continuous (limits = c(50, 250), sec.axis = sec_axis ((~. / tmp2$snd3Bgn + tmp2$snd1Bgn / tmp2$snd3Bgn), breaks = c(-0.1, 0, 0.5, 1, 1.1), name = "normalized sound timing, the lateral is in [0, 1]"))

Figure 3

Pulse profiles for intervocalic laterals produced by speaker F45. The peaks in the beginning of /lˀ/ in individual words show closure due to tongue position in the lateral after a close vowel.

data %>% 
  filter (speaker ==  "F45",
                 word != "j_ilˀ^i_nno",
                 word != "kul_il^o_jd") %>% 
  mutate(plsIntvl = plsIntvl * 1000) %>% 
  ggplot (aes (x = pointsNorm, y = plsIntvl)) +
  geom_line (aes(colour = conditionWord)) +
  xlab ("sound timing, % of total, laterals are in [0, 1]") +
  ylab ("pulse interval duration, ms") +
  geom_vline (xintercept = 1, linetype = "dotted", color = "black") +
  geom_vline (xintercept = 0, linetype = "dotted", color = "black") +
  theme (legend.key.width = unit (0.5, "line"),
         legend.position = "none",
         axis.text.x = element_text(size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
  scale_x_continuous(breaks = c(-2, -1, 0, 1, 2, 3), labels = c("-2", "-1", "0", "1", "2", "3")) +
  scale_color_grey(start = 0.8, end = 0.2) +
  annotate ("text", x = -0.7, y = 35, label = "/vowel/", size = 4.5) +
  annotate ("text", x = 0.5, y = 35, label = "/lateral/", size = 4.5) +
  annotate ("text", x = 2, y = 35, label = "/vowel/", size = 4.5) +
  facet_grid (~conditionNorm~wordType, scales = "free")

Figure 4

Pulse profiles for intervocalic laterals produced by speaker M69. Note that the y-axes have different scales, the words describing sounds always stay on the same level (35 ms).

data %>% 
  filter (speaker ==  "M69") %>% 
  mutate(plsIntvl = plsIntvl * 1000) %>% 
  ggplot (aes (x = pointsNorm, y = plsIntvl)) +
  geom_line (aes(colour = conditionWord)) +
  xlab ("sound timing, % of total, laterals are in [0, 1]") +
  ylab ("pulse interval duration, ms") +
  geom_vline (xintercept = 1, linetype = "dotted", color = "black") +
  geom_vline (xintercept = 0, linetype = "dotted", color = "black") +
  theme (legend.key.width = unit (0.5, "line"),
         legend.position = "none",
         axis.text.x = element_text(size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
  scale_x_continuous(breaks = c(-2, -1, 0, 1, 2, 3), labels = c("-2", "-1", "0", "1", "2", "3")) +
  scale_color_grey(start = 0.8, end = 0.2) +
  annotate ("text", x = -0.7, y = 35, label = "/vowel/", size = 4.5) +
  annotate ("text", x = 0.5, y = 35, label = "/lateral/", size = 4.5) +
  annotate ("text", x = 2, y = 35, label = "/vowel/", size = 4.5) +
  facet_grid (~conditionNorm~wordType, scales = "free")

Figure 5

An example of a “silent” realization of /lˀ/ M69. Within the lateral we can see an area of a flat waveform showing full glottal constriction. The realization is the first individual utterance of /relˀa/ (“hand”).

textgrid_to_df("data/M69_hand_1_utterance.TextGrid") %>% 
  filter(tier %in% c(1, 5)) ->
  tg
draw_sound("data/M69_hand_1_utterance.wav",
           annotation = tg, 
           from = 0.1427, 
           to = 0.674, 
           spectrum_info = FALSE)

Figure 6

Timing of the longest interval between glottal pulses as a proxy for glottal constriction.

# base line: median interval between pulses for /l/ + 3 * SD
yBaseLine <- data %>% filter (wordType == "l") %>%
  select (speaker, plsIntvl) %>%
  group_by (speaker) %>%
  summarize (avIntvl = mean (plsIntvl),
             sdIntvl = sd (plsIntvl))

yBaseLine <- data.frame (yBaseLine, yIntercept = (yBaseLine$avIntvl + yBaseLine$sdIntvl * 3) * 1000)


dataWrd %>% 
  mutate(plsMax = plsMax * 1000) %>%  # convert to milliseconds
  ggplot () +
  geom_point (aes (x = plsMaxPointNorm, y = plsMax, color = conditionNorm, shape = wordType), size = 1.5) +
  scale_color_grey(start = 0, end = 0.7) +
  geom_vline (xintercept = 0, linetype = "dashed", size = 0.5) +
  geom_vline (xintercept = 1, linetype = "dashed", size = 0.5) +
  geom_vline (xintercept = 0.33, linetype = "dotted", size = 0.5) +
  geom_vline (xintercept = 0.66, linetype = "dotted", size = 0.5) +
  geom_hline (data = yBaseLine, aes (yintercept = yIntercept), linetype = "dashed", size = 0.4) +
  ylab ("duration of the longest interval between pulses, ms") +
  xlab ("normalized sound timing, [0; 1] interval between the vertical lines corresponds to the lateral") +
  theme(legend.title = element_blank(),
        axis.text.x = element_text(size = 12),
        axis.text.y = element_text(size = 12),
        axis.title.x = element_text(size = 12),
        axis.title.y = element_text(size = 12),
        legend.text = element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "bottom") +
  scale_x_continuous(breaks = c(-2, -1, 0, 0.33, 0.66, 1, 2, 3), labels = c("-2", "-1", "0", "1/3","2/3", "1", "2", "3")) +
  ylim (0, 0.22 * 1000) +
  annotate ("text", x = -1, y = 0.2 * 1000, label = "/vowel/", size = 5) +
  annotate ("text", x = 0.5, y = 0.2 * 1000, label = "/lateral/", size = 5) +
  annotate ("text", x = 2, y = 0.2 * 1000, label = "/vowel/", size = 5) +
  facet_wrap (~speaker, ncol = 2)

Figure 7

Intensities of glottalized and modal laterals compared (only /la/ and /lˀa/ segments).

# only laterals followed by /a/: snd3 == "a*" | snd3 == "^a*"
# no carrier phrase: too few realizations
 
dataWrd %>% 
  filter((substr(snd3, 1,1) == "a" | substr(snd3, 1,2) == "^a"),
         condition != "Carrier phrase") %>% 
  ggplot (aes (x = conditionNorm2, y = snd2IntNorm, color = conditionNorm2)) +
  geom_point (position = position_jitter(w = 0.2, h = 0)) +
  geom_boxplot (outlier.alpha = 0) +
  theme (legend.title = element_blank(),
         axis.text.x=element_text(angle=55, hjust=1, size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_blank(),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12),
         legend.position = "none") +
  ylab ("Normalized intensity, dB") +
  scale_color_grey(start=0.6, end=0.2) +
  facet_wrap (~speaker, ncol=5)

Figure 8

An example of a lower-intensity modal realization of /lˀ/ by the speaker M52 in the carrier phrase. The word is /rolˀal/ (“clothes”).

textgrid_to_df("data/M52_clothes_carrier_phrase.TextGrid") %>% 
  filter(tier %in% c(1, 5)) %>% 
  mutate(content = str_replace(content, "\\^a", "a")) ->
  tg
draw_sound("data/M52_clothes_carrier_phrase.wav",
           annotation = tg, 
           from = 0.120225, 
           to = 0.392, 
           spectrum_info = FALSE)

Table 4

dataWrd %>% 
  filter(str_detect(conditionNorm, "(Individual words)|(Free narrative)")) %>%
  group_by(conditionNorm, snd2) %>% 
  summarise(n = n(), 
            mean = mean(snd2IntNorm), 
            sd = sd(snd2IntNorm)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sd) %>% 
  pivot_wider(names_from = c(snd2, stats), values_from = "values")

Figure 9

dataWrd %>% 
  mutate(lDur = lDur * 1000) %>%  # convert to milliseconds
  ggplot (aes (x = conditionNorm2, y = lDur, color = conditionNorm2)) +
  geom_point (position = position_jitter(w = 0.2, h = 0)) +
  geom_boxplot (outlier.alpha = 0) +
  theme (legend.title = element_blank(),
         axis.text.x=element_text(angle=55, hjust=0.95, size = 12),
         axis.text.y = element_text(size = 12),
         axis.title.x = element_blank(),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12),
         legend.position = "none") +
  ylab ("sound duration, ms") +
  scale_color_grey(start=0.6, end=0.2) +
  facet_wrap (~speaker, ncol=5)

Table 5

dataWrd %>% 
  mutate(lDur = lDur * 1000) %>%  # convert to milliseconds
  group_by(conditionNorm, snd2) %>% 
  summarise(n = n(), 
            mean = mean(lDur), 
            sd = sd(lDur)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sd) %>% 
  pivot_wider(names_from = c(snd2, stats), values_from = "values")

Figure 10.

Differences between intensities of A1-H1.

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
    ggplot() +
  geom_point (aes (y = a1h1Int, x = conditionNorm2, color = factor("red", label = "A1-H1")), shape = 21, position = position_jitter(w = 0.2, h = 0)) +
  geom_boxplot (aes (y = a1h1Int, x = conditionNorm2, color = factor("red", label = "A1-H1")), outlier.alpha = 0) +
  theme (axis.text.x=element_text(angle=60, hjust=1, size = 12),
         legend.position = "none",
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
  ylab ("Intensity difference, dB") +
  scale_color_grey(start = 0.5, end = 0.2) +
  labs (color = "") +
  xlab ("") +
  facet_wrap (~speaker)

Figure 11.

Differences between intensities of A2-H1.

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
  ggplot() +
  geom_point (aes (y = a2h1Int, x = conditionNorm2, color = factor("green", label = "A2-H1")), shape = 22, position = position_jitter(w = 0.2, h = 0)) +
  geom_boxplot (aes (y = a2h1Int, x = conditionNorm2, color = factor("green", label = "A2-H1")), outlier.alpha = 0) +
  theme (axis.text.x=element_text(angle=60, hjust=1, size = 12),
         legend.position = "none",
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
  ylab ("Intensity difference, dB") +
  scale_color_grey(start = 0.5, end = 0.2) +
  labs (color = "") +
  xlab ("") +
  facet_wrap (~speaker)

Figure 12.

Differences between intensities of H2-H1.

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
  ggplot() +
  geom_point (aes (y = h2h1Int, x = conditionNorm2, color = factor("blue", label = "H2-H1")), shape = 22, position = position_jitter(w = 0.2, h = 0)) +
  geom_boxplot (aes (y = h2h1Int, x = conditionNorm2, color = factor("blue", label = "H2-H1")), outlier.alpha = 0) +
  theme (axis.text.x=element_text(angle=60, hjust=1, size = 12),
         legend.position = "none",
         axis.text.y = element_text(size = 12),
         axis.title.x = element_text(size = 12),
         axis.title.y = element_text(size = 12),
         strip.text.x = element_text(size = 12),
         strip.text.y = element_text(size = 12)) +
#  ggtitle ("H2-H1 intensity difference") +
  ylab ("Intensity difference, dB") +
  scale_color_grey(start = 0.5, end = 0.2) +
  labs (color = "") +
  xlab ("") +
  facet_wrap (~speaker)

Table 7.

A1-H1 intensity

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
  group_by(conditionNorm, snd2) %>% 
  summarise(n = n(), 
            mean = mean(a1h1Int), 
            sd = sd(a1h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sd) %>% 
  pivot_wider(names_from = c(snd2, stats), values_from = "values")

Table 8.

A2-H1 intensity

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
  group_by(conditionNorm, snd2) %>% 
  summarise(n = n(), 
            mean = mean(a2h1Int), 
            sd = sd(a2h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sd) %>% 
  pivot_wider(names_from = c(snd2, stats), values_from = "values")

Table 9.

H2-H1 intensity

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"), 
          is.na (a1h1Int) == FALSE,
          conditionNorm != "Carrier phrase",
          !(speaker == "M69" & conditionNorm == "Individual words"),
          !(speaker == "M50" & conditionNorm == "Individual words")) %>% 
  mutate(speaker = factor (speaker, levels = c("F45", "F30", "F49", "M52", "M60", "M69", "M35", "M40", "M75"))) %>% 
  group_by(conditionNorm, snd2) %>% 
  summarise(n = n(), 
            mean = mean(h2h1Int), 
            sd = sd(h2h1Int)) %>% 
  pivot_longer(names_to = "stats", values_to = "values", n:sd) %>% 
  pivot_wider(names_from = c(snd2, stats), values_from = "values")

Figure 13.

Effect plots for laterals’ intensity: condition and sound are independent variables, speaker is a random intercept.

dataWrd %>% 
  filter ((substr (snd3,1,1) == "a" | substr (snd3,2,2) == "a"),
          conditionNorm != "Carrier phrase") %>% 
  mutate(cond = conditionNorm,
         speaker = factor(speaker,
                          levels = c ("F30", "M35", "M40", "F45", "F49", "M50",
                                      "M52", "M60", "M69", "M75")),
         intensity = snd2IntNorm,
         sound = wordType,
         sound = ifelse(sound == "l", "l", "glottolised l")) %>% 
  lmer (intensity ~ cond*sound + (1|speaker), data = .) %>% 
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: intensity ~ cond * sound + (1 | speaker)
   Data: .

REML criterion at convergence: 855.1

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.4672 -0.4874  0.1502  0.6561  1.9824 

Random effects:
 Groups   Name        Variance Std.Dev.
 speaker  (Intercept)  3.972   1.993   
 Residual             12.930   3.596   
Number of obs: 157, groups:  speaker, 10

Fixed effects:
                            Estimate Std. Error       df t value Pr(>|t|)    
(Intercept)                  68.3717     0.9087  17.8625  75.243  < 2e-16 ***
condIndividual words         -6.8749     0.9861 106.3475  -6.972 2.75e-10 ***
soundl                        1.6607     0.8911 144.6487   1.864   0.0644 .  
condIndividual words:soundl   6.5213     1.1874 144.3826   5.492 1.74e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
            (Intr) cndInw soundl
cndIndvdlwr -0.576              
soundl      -0.338  0.316       
cndIndwrds:  0.253 -0.500 -0.751

Figure 14.

Effect plots for creak (as proxied by maximal interval between 2 consecutive glottal pulses): condition and sound are independent variables, speaker is a random intercept.

Figure 15.

Effect plots for laterals’ duration: condition and sound are independent variables, speaker is a random intercept.

Figure 16.

Effect plots for intensity difference between the harmonic closest to A1 and H1: condition and sound are independent variables, speaker is a random intercept.

Figure 17.

Effect plots for intensity difference between the harmonic closest to A2 and H1: condition and sound are independent variables, speaker is a random intercept.

Figure 18.

Effect plots for intensity difference between H2 and H1: condition and sound are independent variables, speaker is a random intercept.

Package versions

packages <- c("rmarkdown", "tidyverse", "phonfieldwork", "lme4", "lmerTest", "effects")
map_dfr(packages, function(i){
  tibble(package = i,
         version = paste(packageVersion(i), sep = "."))
}) %>% 
  arrange(package)